/*
 * Copyright (c) 2009 Nathaniel Houghton <nathan@brainwerk.org>
 *                    Robert Kirchgessner <rkirchge@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for
 * any purpose with or without fee is hereby granted, provided that
 * the above copyright notice and this permission notice appear in all
 * copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
 * OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 *
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

#include "config.h"
#include "elm_dtc.h"
#include "nvm.h"
#include "elm.h"
#include "lcd.h"
#include "mode.h"
#include "obd_pid.h"
#include "ui.h"
#include "util.h"

#define DEBOUNCE_DELAY 20

#define SW_MSK 0x1E
#define LCD_MSK 0x01

/*First Level Menu*/
MENU_RES_DYNAMIC(MENU_ID_ROOT, menu_dynamic_root, MENU_ID_CHECK_DTC, MENU_ID_CHECK_DTC, MENU_ID_CHECK_DTC, NULL );
	
/*First Level Menu*/
MENU_RES_STATIC(MENU_ID_CHECK_DTC, "\x7F Check DTCs \x7E", MENU_ID_NVM_ERASE, MENU_ID_FLASH_STATUS, MENU_ID_DTCMIL, menu_dtc_update );
MENU_RES_DYNAMIC(MENU_ID_FLASH_STATUS, menu_dynamic_flash, MENU_ID_CHECK_DTC, MENU_ID_NVM_ERASE, MENU_ID_FLASH_STATUS, NULL);
MENU_RES_STATIC(MENU_ID_NVM_ERASE, "\x7F Erase Flash \x7E", MENU_ID_FLASH_STATUS, MENU_ID_CHECK_DTC, MENU_ID_ROOT, erase_nvm_cmd );
	
/*Second Level Menus*/
MENU_RES_DYNAMIC(MENU_ID_DTCMIL, menu_dynamic_dtcmil, MENU_ID_BACK, MENU_ID_VIEWDTC, MENU_ID_DTCMIL, NULL );
MENU_RES_STATIC(MENU_ID_VIEWDTC, "\x7F View DTC \x7E", MENU_ID_DTCMIL, MENU_ID_CLEARDTC, MENU_ID_DTC, NULL );
MENU_RES_STATIC(MENU_ID_CLEARDTC, "\x7F Clear DTC \x7E", MENU_ID_VIEWDTC, MENU_ID_BACK, MENU_ID_CLEAREDDTC, elm_dtc_clear );
MENU_RES_STATIC(MENU_ID_CLEAREDDTC, "DTCs Cleared!", MENU_ID_CLEAREDDTC, MENU_ID_CLEAREDDTC, MENU_ID_CLEARDTC, NULL );
MENU_RES_STATIC(MENU_ID_BACK, "\x7F Back \x7E", MENU_ID_CLEARDTC, MENU_ID_DTCMIL, MENU_ID_ROOT, NULL );
	
/*Third Level Menu*/
MENU_RES_DYNAMIC(MENU_ID_DTC, menu_dtc_current, MENU_ID_DTCLEFT, MENU_ID_DTCRIGHT, MENU_ID_VIEWDTC, NULL );

/*Fourth Level Menu*/
/*If current menu is DTCLEFT, then menu_dtc_current calls prev(), otherwise calls next()*/
MENU_RES_DYNAMIC(MENU_ID_DTCLEFT,  menu_dtc_current, MENU_ID_DTCLEFT, MENU_ID_DTCRIGHT, MENU_ID_VIEWDTC, NULL );
MENU_RES_DYNAMIC(MENU_ID_DTCRIGHT, menu_dtc_current, MENU_ID_DTCLEFT, MENU_ID_DTCRIGHT, MENU_ID_VIEWDTC, NULL );
	
static struct menu ui_menu = {MENU_ID_ROOT, { MENU_ENTRY(MENU_ID_ROOT), MENU_ENTRY(MENU_ID_CHECK_DTC), MENU_ENTRY(MENU_ID_FLASH_STATUS),
											  MENU_ENTRY(MENU_ID_NVM_ERASE), MENU_ENTRY(MENU_ID_DTCMIL), MENU_ENTRY(MENU_ID_VIEWDTC),
											  MENU_ENTRY(MENU_ID_CLEARDTC), MENU_ENTRY(MENU_ID_CLEAREDDTC), MENU_ENTRY(MENU_ID_BACK),MENU_ENTRY(MENU_ID_DTC), 
											  MENU_ENTRY(MENU_ID_DTCLEFT),  MENU_ENTRY(MENU_ID_DTCRIGHT) }};

static volatile uint8_t buttons;
static uint8_t	update = 1;
	
struct menu_entry *menu_get_entry( uint8_t id );

void
menu(struct mode *mode, uint8_t init)
{	
	struct menu_entry * current;
	struct static_menu_entry * static_current;
	struct dynamic_menu_entry * dynamic_current;
	char buf[64];	
	
	current = menu_get_entry( ui_menu.current ); /*Get the current menu entry*/
	
	if( update || init ) 
	{
		snprintf(buf, sizeof(buf), "Mode: %s",
			mode_str[mode->mode]);
		lcd_update(&lcd, 0, buf);
		lcd_update(&lcd, 1, "");
		
		update = 0;
		
		if( current->type == MENU_ENTRY_STATIC ) /*Static menus have a constant string*/
		{
			static_current = (struct static_menu_entry*)current;
			lcd_update(&lcd, 1,  static_current->disp );	
		}
		else if( current->type == MENU_ENTRY_DYNAMIC ) /*Static menus have output strings generated by functions*/
		{
			dynamic_current = (struct dynamic_menu_entry*)current;
			dynamic_current->disp();
		}
	}

	if ( btn_down(BUTTON_LEFT) )
	{
		update = 1;
		menu_set_current( current->left );
	}

	if ( btn_down(BUTTON_ENTER) )
	{
		update = 1;
		menu_set_current( current->enter );	
		
		if( current->fp != NULL ) /*If there is a function to be executed on enter button, then run it*/
			current->fp();
	}

	if ( btn_down(BUTTON_RIGHT) )
	{
		update = 1;
		menu_set_current( current->right );	
	}
}

void 
menu_dynamic_root(void)
{
	lcd_update(&lcd, 1,  "Root Menu" );
	_delay_ms( 300 );
	menu_set_current( MENU_ID_CHECK_DTC );
	update = 1;
}	

void 
menu_dynamic_dtcmil(void)
{
	char buf[21];

	snprintf(buf, sizeof(buf), "%3d DTCs, MIL: %s", elm_dtc_count(), ((elm_dtc_mil())?"ON":"OFF") );
	lcd_update(&lcd, 1,  buf );
}

void 
menu_dtc_update(void)
{
	elm_init_retry( &myelm, PROT_ISO9141 );
	
	if( myelm.initd == 0 )
	{
		menu_set_current( MENU_ID_ROOT );
		update = 1;
		return;
	}
	
	elm_dtc_update(); /*Update the DTC list*/
}

void 
menu_dtc_current(void)
{
	char buf[15];
	char dtc_buf[6];
	
	if( elm_dtc_count() == 0 )
	{
		lcd_update(&lcd, 1,  "No DTCs Reported." );
		_delay_ms(300);
		menu_set_current( MENU_ID_VIEWDTC );
		update = 1;
		return;
	}
	
	if( ui_menu.current == MENU_ID_DTCLEFT )
		elm_dtc_prev();
		
	if( ui_menu.current == MENU_ID_DTCRIGHT )
		elm_dtc_next();
		
	DTC_CODE( dtc_buf, elm_dtc_current() );
	snprintf(buf,sizeof(buf),"Code: %s", dtc_buf);
	
	lcd_update(&lcd, 1,  buf );
}

void 
menu_dynamic_flash(void)
{
	double mem_used;
	char buf[32];
	
	mem_used = (cfg.nvm_start_addr * 100.0 / NVM_PAGE_SIZE) /
	    NVM_NUM_PAGES;
	snprintf(buf, sizeof(buf), "\x7F Mem Used: %.2f%% \x7E", mem_used);
	
	lcd_update(&lcd, 1, buf);
}

void
init_ui(void)
{
	/* enable pull-up resistors */
	PORTA |= 0xF0;
	PORTD |= 0x80;

	_delay_us(30);

	buttons |= (~(PIND & 0x80) >> 7) & LCD_MSK;
	buttons |= (~(PINA & 0xF0) >> 3) & SW_MSK;

	/* mask off pins of interest for b3..1 */
	PCMSK0 = 0xF0;

	/* enable PCINT0 for b3..1 */
	PCICR |= 0x01;
	
	/* mask off pins of interest for lcd_present */
	PCMSK3 = 0x80;

	/* enable PCINT3 for lcd_present */
	PCICR |= 0x08;
	
	/*Build the LCD Menu*/
	//menu_set_current(MENU_ID_ROOT); //Set as default in the global declaration
	
}

uint8_t
btn_down(uint8_t btn)
{
	uint8_t r;

	r = 0;

	_delay_ms(DEBOUNCE_DELAY);
	if (btn == BTN_LCDPRESENT) {
		if (PIND & 0x80)
			buttons &= ~BTN_LCDPRESENT;
	} else if (PINA & 0xF0)
		buttons &= ~((PINA >> 3) & SW_MSK);

	if (buttons & btn)
		r = 1;

	return r;
}

void
lcd_update(struct lcd *lcd, uint8_t line, char *str)
{
	int i;
	char *p;

	if (line == 0) {
		p = lcd->line0;
	} else {
		p = lcd->line1;
	}

	strlcpy(p, str, sizeof(lcd->line0));

	for (i = strlen(p); i < sizeof(lcd->line0) - 1; ++i)
		p[i] = ' ';

	p[i] = '\0';

	lcd_home(0);
	lcd_print(lcd->line0);
	lcd_home(1);
	lcd_print(lcd->line1);
}

void menu_set_current( uint8_t id )
{
	ui_menu.current = id;
}

struct menu_entry *
menu_get_entry( uint8_t id )
{
	int i;
	
	for(i=0;i<MENU_ENTRY_COUNT;++i)
	{
		if( ui_menu.menu_entries[i]->id == id )
			break;
	}

	return ui_menu.menu_entries[i];
}

void
menu_repaint(void)
{
	update = 1;
}

ISR(PCINT0_vect)
{
	/* set button bits (cleared in btn_down) */
	buttons |= (~(PINA & 0xF0) >> 3) & SW_MSK;
}

ISR(PCINT3_vect)
{
	/* set button bits (cleared in btn_down) */
	buttons |= (~(PIND & 0x80) >> 7) & LCD_MSK;
}
